﻿' One node in the diagram. The control is a Button with a redefined control template.
'
' The control templates (and other resources) are specified in Skins/<name>/Resources/DiagramResources.xaml. 
' This is a resource dictionary that is part of the application resources. If the resources
' were specified as part of the control, every instance of the control would allocate memory
' for each resource. Specifying the resources at the application level only allocates one copy
' of the resources which are shared with all instances of the control. The resources could be
' specified in the application's generic dictionary also (themes/generic.xaml).
' 
' Instead of specifying every possible node combination as a resource, only four control templates 
' are specified: Area, Iteration, primary Area, and primary Iteration. Then different brushes are used 
' depending on the node state. For example: sibling brush, related deceased brush, spouse brush.
' This reduces the amount of code in the XAML file, but requires code that determines what control
' template and brush resource to use based on the node's state.
Imports System
Imports System.Windows.Controls
Imports System.Windows
Imports System.Text
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Globalization
Imports System.Collections.ObjectModel

Partial Public Class DiagramNode
    Inherits Button

    Private Class [Const]
        Public Shared OpacityFiltered As Double = 0.15
        Public Shared OpacityNormal As Double = 1.0
        Public Shared AnimationDuration As Double = 300
    End Class

#Region " Fields "

    ' Item object associated with the node.
    Private m_Item As Item

    ' Location of the node, relative to its parent group.
    Private m_location As Point = New Point()

    ' The amount to scale the node. 
    Private m_scale As Double = 1

    ' The type of node.
    Private m_type As NodeType = NodeType.Related

    ' Flag, true if this node is currently filtered. This means
    ' its still displayed but in a dim state.
    Private m_isFiltered As Boolean

#End Region

#Region " properties "

    ''' <summary>
    ''' Get the fill brush for the node based on the node type.
    ''' </summary>
    Public Property NodeFill() As Brush
        Get
            Return GetBrushResource("Fill")
        End Get
        Set(ByVal value As Brush)
            ' do nothing
        End Set
    End Property

    ''' <summary>
    ''' Get the hover fill brush for the node based on the node type.
    ''' </summary>
    Public ReadOnly Property NodeHoverFill() As Brush
        Get
            Return GetBrushResource("HoverFill")
        End Get
    End Property


    ''' <summary>
    ''' Get the stroke brush for the node based on the node type.
    ''' </summary>
    Public ReadOnly Property NodeStroke() As Brush
        Get
            Return GetBrushResource("Stroke")
        End Get
    End Property

    ''' <summary>
    ''' Get the fill brush for the group indicator.
    ''' </summary>
    Public ReadOnly Property GroupFill() As Brush
        Get
            Return GetGroupBrushResource("GroupFill")
        End Get
    End Property

    ''' <summary>
    ''' Get the stroke brush for the group indicator.
    ''' </summary>
    Public ReadOnly Property GroupStroke() As Brush
        Get
            Return GetGroupBrushResource("GroupStroke")
        End Get
    End Property

    ''' <summary>
    ''' Get or set if the node is filtered.
    ''' </summary>
    Public Property IsFiltered() As Boolean
        Get
            Return m_isFiltered
        End Get
        Set(ByVal value As Boolean)
            If Not m_isFiltered = value Then
                ' The filtered state changed, create a new animation.
                m_isFiltered = value
                Dim newOpacity As Double = CDbl(IIf(m_isFiltered, [Const].OpacityFiltered, [Const].OpacityNormal))
                Me.BeginAnimation(OpacityProperty, New DoubleAnimation(Me.Opacity, newOpacity, Helper.GetAnimationDuration([Const].AnimationDuration)))
            End If

        End Set
    End Property

    ''' <summary>
    ''' Item object associated with the node.
    ''' </summary>
    Public Property Item() As Item
        Get
            Return m_Item
        End Get
        Set(ByVal value As Item)
            m_Item = value
            Me.DataContext = Me

            ' Update the template to reflect the gender.
            UpdateTemplate()

            UpdateBottomLabel()
        End Set
    End Property

    ''' <summary>
    ''' Set the scale value of the node.
    ''' </summary>
    Public Property Scale() As Double
        Get
            Return m_scale
        End Get
        Set(ByVal value As Double)
            ' Save the scale value, used later after apply the node template.
            m_scale = value
        End Set
    End Property

    ''' <summary>
    ''' Location of the node relative to the parent group.
    ''' </summary>
    Public Property Location() As Point
        Get
            Return m_location
        End Get
        Set(ByVal value As Point)
            m_location = value
        End Set
    End Property

    ''' <summary>
    ''' Get the center of the node.
    ''' </summary>
    Public ReadOnly Property Center() As Point
        Get
            Return New Point(Location.X + (DesiredSize.Width / 2), Location.Y + (DesiredSize.Height / 2))
        End Get
    End Property

    ''' <summary>
    ''' Get the top center of the node. The center is shifted to the left since the 
    ''' Item drawing is not located in the true center of the node, it's shifted
    ''' to the left due to the shadow.
    ''' </summary>
    Public ReadOnly Property TopCenter() As Point
        Get
            ' The real center of the node.
            Dim point As Point = New Point(Location.X + (Me.DesiredSize.Width / 2), Location.Y)
            ' Shift the center to the left. This is an estimate since we don't 
            ' know the exact position of the Item drawing within the node.
            'Dim itemElement As FrameworkElement = CType(Me.Template.FindName("Item", Me), FrameworkElement)
            'Dim offset As Double = CDbl(IIf(Me.Type = NodeType.Primary, 12, 5))
            'point.X -= (itemElement.ActualWidth / offset)
            'TODO: Figure Out Offset
            Return point
        End Get
    End Property


    ''' <summary>
    ''' Get the top right of the node.
    ''' </summary>
    Public ReadOnly Property TopRight() As Point
        Get
            Return New Point(Location.X + DesiredSize.Width, Location.Y)
        End Get
    End Property

    ''' <summary>
    ''' Get the top left of the node.
    ''' </summary>
    Public ReadOnly Property TopLeft() As Point
        Get
            Return New Point(Location.X, Location.Y)
        End Get
    End Property

    ''' <summary>
    ''' Get the bottom center of the node.
    ''' </summary>
    Public ReadOnly Property BottomCenter() As Point
        Get
            Return New Point(Location.X + (DesiredSize.Width / 2), Location.Y + DesiredSize.Height)
        End Get
    End Property

    ''' <summary>
    ''' Get the left center of the node.
    ''' </summary>
    Public ReadOnly Property LeftCenter() As Point
        Get
            Return New Point(Location.X, Location.Y + (DesiredSize.Height / 2))
        End Get
    End Property


    ''' <summary>
    ''' Get the right center of the node.
    ''' </summary>
    Public ReadOnly Property RightCenter() As Point
        Get
            Return New Point(Location.X + DesiredSize.Width, Location.Y + (DesiredSize.Height / 2))
        End Get
    End Property

    ''' <summary>
    ''' The type of node.
    ''' </summary>
    Public Property Type() As NodeType
        Get
            Return Me.m_type
        End Get
        Set(ByVal value As NodeType)
            Me.m_type = value
            UpdateTemplate()
        End Set
    End Property

#End Region

#Region " dependency properties "


    ''' <summary>
    ''' The text displayed below the node.
    ''' </summary>
    Public Property BottomLabel() As String
        Get
            Return CStr(GetValue(BottomLabelProperty))
        End Get

        Set(ByVal value As String)
            SetValue(BottomLabelProperty, value)
        End Set
    End Property

    Public Shared ReadOnly BottomLabelProperty As DependencyProperty = _
                           DependencyProperty.Register("BottomLabel", _
                           GetType(String), GetType(DiagramNode), _
                           New FrameworkPropertyMetadata(Nothing))

#End Region

#Region " overrides "

    Public Overrides Sub OnApplyTemplate()
        ' The template has been applied to the node. See if the Item drawing needs to be scaled.
        If Not Me.Scale = 1 Then
            ' Scale the Item drawing part of the node, not the entire node.
            Dim ItemElement As FrameworkElement = CType(Me.Template.FindName("Item", Me), FrameworkElement)
            If Not ItemElement Is Nothing Then
                Dim transform As ScaleTransform = New ScaleTransform(Me.Scale, Me.Scale)
                ItemElement.LayoutTransform = transform
            End If
        End If
        ' The template changed, determine if the group
        ' indicator should be displayed.
        UpdateGroupIndicator()
        MyBase.OnApplyTemplate()
    End Sub

#End Region

    ''' <summary>
    ''' Return the brush resouse based on the node type.
    ''' </summary>
    Private Function GetBrushResource(ByVal part As String) As Brush
        ' Format string, the resource is in the XAML file.
        'Dim resourceName As String = String.Format( _
        '    CultureInfo.InvariantCulture, "{0}{1}{2}{3}", _
        '    IIf(Me.Item.IsAreaNode, "Area", "Iteration"), _
        '    Me.Item.Type.ToString(), _
        '    IIf(Me.Item.HasChildNodes, "Subs", "NoSubs"), _
        '    part)

        Dim resourcename As String = ""
        Select Case Item.Type
            Case ItemType.Node
                Dim Item As NodeItem = CType(Me.Item, NodeItem)
                resourcename = String.Format( _
                            CultureInfo.InvariantCulture, "{0}{1}{2}{3}", _
                            IIf(Item.Value.IsAreaNode, "Area", "Iteration"), _
                            Me.Type.ToString(), _
                            IIf(Item.Value.HasChildNodes, "Living", "Deceased"), _
                            part)
            Case ItemType.Project
                Dim Item As ProjectItem = CType(Me.Item, ProjectItem)
                resourcename = String.Format( _
                            CultureInfo.InvariantCulture, "{0}{1}{2}", _
                            "Project", _
                            Me.Type.ToString(), _
                            part)
        End Select
        Dim b As Brush = CType(TryFindResource(resourcename), Brush)
        If b Is Nothing Then
            Debug.Print("GetBrushResource: Failed to get '{0}' on template '{1}' for part '{2}' of '{3}'", resourcename, m_SelectedTemplate, part, Me.Item.Type.ToString)
        End If
        Return b
    End Function

    Private Function GetGroupBrushResource(ByVal part As String) As Brush
        ' Format string, the resource is in the XAML file.
        'Dim resourceName As String = String.Format(CultureInfo.InvariantCulture, "{0}{1}", Me.Type.ToString(), part)
        Dim resourceName As String = String.Format( _
            CultureInfo.InvariantCulture, _
            "{0}{1}", _
            Me.Type.ToString(), _
            part)
        Dim b As Brush = CType(TryFindResource(resourceName), Brush)
        If b Is Nothing Then
            Debug.Print("GetGroupBrushResource: Loading '{0}' on template '{1}' for part '{2}' of '{3}", resourceName, m_SelectedTemplate, part, Me.Item.Type.ToString)
        End If
        Return b
    End Function


    Private m_SelectedTemplate As String = ""
    ''' <summary>
    ''' Update the node template based on the node type.
    ''' </summary>
    Private Sub UpdateTemplate()
        ' Determine the node template based on node properties.
        'Dim template As String = String.Format( _
        '    CultureInfo.InvariantCulture, "{0}{1}NodeTemplate", _
        '    IIf(Item.IsAreaNode, "Area", "Iteration"), _
        '    IIf(Item.HasChildNodes, "Subs", "NoSubs"))
        Dim template As String = ""
        Select Case Item.Type
            Case ItemType.Node
                Dim Item As NodeItem = CType(Me.Item, NodeItem)
                template = String.Format( _
                    CultureInfo.InvariantCulture, "{0}{1}NodeTemplate", _
                    IIf(Item.Value.IsAreaNode, "Area", "Iteration"), _
                    IIf(Me.Type = NodeType.Primary, "Primary", ""))
            Case ItemType.Project
                Dim Item As ProjectItem = CType(Me.Item, ProjectItem)
                template = String.Format( _
                   CultureInfo.InvariantCulture, "{0}{1}NodeTemplate", _
                   "Project", _
                   IIf(Me.Type = NodeType.Primary, "Primary", ""))
        End Select
        ' Assign the node template.   
        m_SelectedTemplate = template
        Me.Template = CType(FindResource(template), ControlTemplate)
        'MsgBox(template)
    End Sub

    ''' <summary>
    ''' Hide or show the group indicator for this node.
    ''' </summary>
    Private Sub UpdateGroupIndicator()
        ' Primary templates don't have the group xaml section.
        If Me.Type = NodeType.Primary Then
            Return
        End If

        ' Determine if the group indicator should be displayed.
        Dim isGrouping As Boolean = ShouldDisplayGroupIndicator()

        Dim element As FrameworkElement = CType(Me.Template.FindName("Group", Me), FrameworkElement)
        If Not element Is Nothing Then
            element.Visibility = CType(IIf(isGrouping, Visibility.Visible, Visibility.Collapsed), Windows.Visibility)
        End If


    End Sub



    ''' <summary>
    ''' Return true if the group indicator should be displayed.
    ''' </summary>
    Private Function ShouldDisplayGroupIndicator() As Boolean
        ' Primary and related nodes never display the group indicator.
        If Me.Type = NodeType.Primary OrElse Me.Type = NodeType.Related Then
            Return False
        End If

        Dim show As Boolean = False

        Select Case Me.Type
            ' Sibling - if have spouse, or children.
            Case NodeType.Sibling
                If Me.Item.Children.Count > 0 Then
                    show = True
                End If
                Exit Select
                ' Half sibling - like sibling, but also inherits the 
                ' group status from all parents.
            Case NodeType.SiblingLeft
            Case NodeType.SiblingRight
                If Me.Item.Children.Count > 0 Then
                    show = True
                End If
                Exit Select
        End Select
        Return show
    End Function

    ''' <summary>
    ''' Update the bottom label which contains the name, year range and age.
    ''' </summary>
    Private Sub UpdateBottomLabel()
        'Dim label As String = String.Format(CultureInfo.CurrentCulture, "{0}\r{1}", Me.Item.Name, Me.Item.Path)
        Dim label As String = String.Format( _
            CultureInfo.CurrentCulture, _
            "{0}" & vbCrLf & "{1}", _
            Me.Item.Id, _
            Me.Item.Name)
        Me.BottomLabel = Me.Item.Name
    End Sub


End Class
